package com.eece411.project.p2;

import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;

public class ThreadSck implements Runnable {
	// Base protocol info
	private static final int KEY_LENGHT=32;
	private static final int DATA_LENGHT=1024;
	// Network obj
	private Socket csocket;
	// Time of creation of the thread
	private long startTime;
	// Action to do
	private byte command;
	// Verbose var
	private String str="";
	
	// Constructor
	ThreadSck(byte command, Socket socket, long time) {
		this.csocket = socket;
		this.command = command;
		this.startTime = time;
	}
	public void run() {
		try {
			if (Routing.getInstance()!=null){
				byte[] key = new byte[KEY_LENGHT];
				int totalBytesRcvd=0;
				// Read until the expected length of the header or timeout
				while (totalBytesRcvd < key.length && ((System.currentTimeMillis()-startTime)<1000)) {
					int bytesRcvd;
					if ((bytesRcvd = this.csocket.getInputStream().read(key, totalBytesRcvd,  
							key.length - totalBytesRcvd)) == -1)
						throw new SocketException("Connection close prematurely");
					totalBytesRcvd += bytesRcvd;
				}
				// Timeout --- If client make no progress in 1 sec
				if (((System.currentTimeMillis()-startTime)<1010)){
					if(((command & 0xFF) == 0x01)){
						this.put(key);
					} else if(((command & 0xFF) == 0x02)){
						this.get(key);
					} else if(((command & 0xFF) == 0x03)){
						this.remove(key);
					} else if(((command & 0xFF) == 0x20)){
						this.containsKey(key);
					} else {
						// Command not found -> error 5
						csocket.getOutputStream().write((byte) 5);
						str+="--- Command not found || ";
						this.csocket.close();
					}
				} else {
					// Timeout for client ->  Error 21
					csocket.getOutputStream().write((byte) 21);
					str+="--- Kick clien || t";
					this.csocket.close();
				}
			} else {
				// DataStore Internal Error ->  Error 4
				csocket.getOutputStream().write((byte) 4);
				str+="--- KVStore Error || ";
				this.csocket.close();
			}
		} catch (IOException e) {
			str+="Error in the communication || ";
		} catch (OutOfMemoryError E) {
			System.err.println("Out of memory");
			try {
				csocket.getOutputStream().write((byte) 2);
				csocket.close();
			} catch (IOException e) {
				str+="Error in the communication || ";
			}
		}
		// Remove active connection form the count
		Routing.getInstance().disconnectClient();
		System.out.println(str+="Req served in : "+(System.currentTimeMillis()-startTime)+" || Client connected : "+Routing.getInstance().getclientCount());
	}
	
	private void put(byte[] key) throws SocketException, IOException{
		// Bytes Array of the DATA
		byte[] data = new byte[DATA_LENGHT];
		int totalBytesRcvd2 = 0;
		// Read until the expected length of data or time out
		while (totalBytesRcvd2 < data.length && ((System.currentTimeMillis()-startTime)<2000)) {
			int bytesRcvd;
			if ((bytesRcvd = this.csocket.getInputStream().read(data, totalBytesRcvd2,  
					data.length - totalBytesRcvd2)) == -1)
				throw new SocketException("Connection close prematurely");
			totalBytesRcvd2 += bytesRcvd;
		}
		if (((System.currentTimeMillis()-startTime)<2020)){
			// Put data -> 2 error and 0 ok
			if(Routing.getInstance().put(key, data)){
				str+="--- Put ok || ";
				this.csocket.getOutputStream().write((byte) 0);
				this.csocket.close(); 
			} else {
				str+="--- Put not ok || ";
				this.csocket.getOutputStream().write((byte) 2);
				this.csocket.close(); 
			}
		} else {
			// Timeout for client ->  Error 21
			csocket.getOutputStream().write((byte) 21);
			str+="--- Kick client || ";
			this.csocket.close();
		}
	}
	
	private void get(byte[] key) throws IOException{
		byte[] data = Routing.getInstance().get(key);
		// Get data -> 1 error and 0 ok
		if (data!=null){
			this.csocket.getOutputStream().write((byte)0);
			this.csocket.getOutputStream().write(data);
			str+="--- Get ok || ";
			this.csocket.close();
			} else {
				this.csocket.getOutputStream().write((byte) 1);
				str+="--- Error in Get || ";
				this.csocket.close();
			}
	}
	
	private void remove(byte[] key) throws IOException{
		// Remove data -> 1 orror and 0 ok
	  	if(Routing.getInstance().remove(key)){
	  		str+="--- Remove ok || ";
				this.csocket.getOutputStream().write((byte) 0);
				this.csocket.close();
	  	} else {
	  		str+="--- Remove not ok || ";
				this.csocket.getOutputStream().write((byte) 1);
				this.csocket.close();
	  	}
	}
	
	private void containsKey(byte[] key) throws IOException{
		if (Routing.getInstance().containsKey(key)){
			csocket.getOutputStream().write((byte) 0);
			str+="--- Has key || ";
			this.csocket.close();
		} else {
			csocket.getOutputStream().write((byte) 1);
			str+="--- Doesnt has key || ";
			this.csocket.close();
		}
	}
}
